﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using Sripirom.ATS.Core.Configuration;
using Sripirom.ATS.Core.ExceptionHandler;
using Sripirom.ATS.Core.Logging;
using Sripirom.ATS.Core.ServiceModel.Description;
using Sripirom.ATS.Core.Validator;

namespace Sripirom.ATS.Core.Validator
{
    public class AtsMessageValidatesObjectValidator : IObjectValidator
    {
        private readonly PropertiesCollection _messageValidates;
   
        public AtsMessageValidatesObjectValidator(PropertiesCollection messageValidates)
        {
            _messageValidates = messageValidates;
        }
        public IEnumerable<ValidationResult> Validate(object value)
        {
            LoggerFactory.CreateLog().Debug("I: Validate");
            if (value == null)
                yield break;

            GetValidationResults(value.GetType(), value);
            //foreach (var validationResult in GetValidationResults(value.GetType(), value))
            //    yield return validationResult;

            LoggerFactory.CreateLog().Debug("O: Validate");
        }

        /// <summary>
        ///     Validates the properties
        /// </summary>
        /// <param name="propertyDescriptor">The property descriptor.</param>
        /// <param name="container">The container.</param>
        /// <returns></returns>
        private void ValidateProperties(PropertyDescriptor propertyDescriptor, object container)
        {
            LoggerFactory.CreateLog().Debug("I: ValidateProperties Name=" + propertyDescriptor.Name);
            var value = propertyDescriptor.GetValue(container);

            if (value != null)
                GetValidationResults(propertyDescriptor.PropertyType, value);


            var validateConfig = _messageValidates[propertyDescriptor.Name];
            if (validateConfig != null) 
            {
                MessageType messageType = (MessageType)Enum.Parse(typeof(MessageType), validateConfig.MessageType);
                switch (messageType)
                {
                    case MessageType.STRING:
                        if(String.IsNullOrEmpty(validateConfig.PossibleValues))
                            Validate((string)value, validateConfig.FieldName, validateConfig.IsMandatory, messageType, validateConfig.MinLen, validateConfig.MaxLen);
                        else
                            Validate((string)value, validateConfig.FieldName, validateConfig.IsMandatory, messageType, validateConfig.MinLen, validateConfig.MaxLen, validateConfig.PossibleValues.Split(','));
                        break;
                    case MessageType.NUMBER:
                        Validate((string)value, validateConfig.FieldName, validateConfig.IsMandatory, messageType, validateConfig.MinLen, validateConfig.MaxLen, validateConfig.PossibleValues.Split(','), validateConfig.Scale, validateConfig.Precision);
                        break;
                    case MessageType.DECIMAL:
                        Validate((decimal)value, validateConfig.FieldName, validateConfig.IsMandatory, messageType, validateConfig.Scale, validateConfig.Precision);
                        break;
                    case MessageType.DATE:
                        Validate((string)value, validateConfig.FieldName, validateConfig.IsMandatory, messageType, validateConfig.MinLen, validateConfig.MaxLen);
                        break;
                    case MessageType.TIME:
                        Validate((string)value, validateConfig.FieldName, validateConfig.IsMandatory, messageType, validateConfig.MinLen, validateConfig.MaxLen);
                        break;
                    case MessageType.DATETIME:
                        Validate((string)value, validateConfig.FieldName, validateConfig.IsMandatory, messageType, validateConfig.MinLen, validateConfig.MaxLen);
                        break;
                    default:
                        break;
                }
            //}
        
            //var context = new ValidationContext(container, null, null)
            //{
            //    DisplayName = propertyDescriptor.DisplayName,
            //    MemberName = propertyDescriptor.Name
            //};

            //foreach (var validationAttribute in propertyDescriptor.Attributes.OfType<ValidationAttribute>())
            //{
            //    var result = validationAttribute.GetValidationResult(value, context);

            //    if (result != ValidationResult.Success)
            //        yield return result;
            }

            //if (value != null)
            //    GetValidationResults(propertyDescriptor.PropertyType, value);
                //foreach (var validationResult in GetValidationResults(propertyDescriptor.PropertyType, value))
                //    yield return validationResult;

            LoggerFactory.CreateLog().Debug("O: ValidateProperties");
        }

        /// <summary>
        ///     Gets the validation results
        /// </summary>
        /// <param name="propertyType">Type of the property</param>
        /// <param name="value">The value.</param>
        /// <returns></returns>
        private void GetValidationResults(Type propertyType, object value)
        {
            LoggerFactory.CreateLog().Debug("I: GetValidationResults");
            var enumerable = value as IEnumerable;

            if (enumerable != null)
            {
                foreach (var item in enumerable)
                {
                    Validate(item);
                    //foreach (var result in Validate(item))
                    //    yield return result;
                }
            }

            var properties = TypeDescriptor.GetProperties(propertyType)
                .Cast<PropertyDescriptor>()
                .Where(p => !p.IsReadOnly);

            foreach (var property in properties)
            {
                //var validateConfig = _messageValidates[property.Name];
                //if (validateConfig != null)
                    ValidateProperties(property, value);
                //foreach (var result in ValidateProperties(property, value))
                //    yield return result;
            }
            LoggerFactory.CreateLog().Debug("O: GetValidationResults");
        }



        #region Validation
        public void Validate(String value, String fieldName, bool isMandatory, MessageType type, int minLen, int maxLen, String[] possibleValues)
        {
            this.Validate(value, fieldName, isMandatory, type, minLen, maxLen, possibleValues, -1, -1);
        }

        public void Validate(String value, String fieldName, bool isMandatory, MessageType type, String[] possibleValues)
        {
            this.Validate(value, fieldName, isMandatory, type, -1, -1, possibleValues, -1, -1);
        }

        public void Validate(String value, String fieldName, bool isMandatory, MessageType type, int minLen, int maxLen)
        {
            this.Validate(value, fieldName, isMandatory, type, minLen, maxLen, null, -1, -1);
        }

        public void Validate(Decimal? value, String fieldName, bool isMandatory, MessageType type, int scale, int precision)
        {
            this.Validate(value.ToString(), fieldName, isMandatory, type, -1, -1, null, scale, precision);
        }

        private void Validate(String value, String fieldName, bool isMandatory, MessageType type, int minLen, int maxLen
                                , String[] possibleValues, int scale, int precision)
        {
            // Validate Mandatory
            if (isMandatory && !ValidateMandatory(value))
            {
                LoggerFactory.CreateLog().LogError("Invaild Mandatory Fields : : " + fieldName + " is empty");
                throw new MessageException(Errors.MANDATORY_REQUIRED, new string[] { fieldName });
            }
            // Check length
            if (maxLen != -1)
            {
                switch (ValidateLength(value, minLen, maxLen))
                {
                    case -1:
                        LoggerFactory.CreateLog().LogError("Field : : " + fieldName + "'s length is shorter than required.");
                        throw new MessageException(Errors.LENGTH_LESS_THAN_MINIMUM, new string[] { fieldName });
                    case 1:
                        LoggerFactory.CreateLog().LogError("Field : : " + fieldName + "'s length is longer than required");
                        throw new MessageException(Errors.LENGTH_GREATER_THAN_MAXIMUM, new string[] { fieldName });
                    case 0:
                        break;
                }
            }
            // Validate Type
            if (MessageType.NUMBER.Equals(type) && !ValidateNumber(value))
            {
                LoggerFactory.CreateLog().LogError("Validate Number Only FAILED : : " + fieldName + " is not valid");
                throw new MessageException(Errors.INVALID_DATA_TYPE, new string[] { fieldName });
            }
            else if (MessageType.DATE.Equals(type) && !ValidateDate(value))
            {
                LoggerFactory.CreateLog().LogError("Invaild Fields : : " + fieldName + " invalid format");
                throw new MessageException(Errors.FORMAT_NOT_CORRECT, new string[] { fieldName });
            }
            else if (MessageType.TIME.Equals(type) && !ValidateTime(value))
            {
                LoggerFactory.CreateLog().LogError("Invaild Fields : : " + fieldName + " invalid format");
                throw new MessageException(Errors.FORMAT_NOT_CORRECT, new string[] { fieldName });
            }
            else if (MessageType.DATETIME.Equals(type) && !ValidateDateTime(value))
            {
                LoggerFactory.CreateLog().LogError("Invaild Fields : : " + fieldName + " invalid format");
                throw new MessageException(Errors.FORMAT_NOT_CORRECT, new string[] { fieldName });
            }
            else if (MessageType.DECIMAL.Equals(type))
            {
                // Check decimal value
                if (!ValidateDecimal(value))
                {
                    LoggerFactory.CreateLog().LogError("Validate Decimal FAILED : : " + fieldName + " is not valid");
                    throw new MessageException(Errors.FORMAT_NOT_CORRECT, new string[] { fieldName });
                }
                // Check precision & scale
                if (!ValidateDecimal(value, scale, precision))
                {
                    LoggerFactory.CreateLog().LogError("Field : : " + fieldName + "'s incurrect precision and scale.");
                    throw new MessageException(Errors.LENGTH_NOT_EQUAL_FIXED, new string[] { fieldName });
                }
            }
            // Check Possible Values
            if (!IsMatch(value, possibleValues))
            {
                LoggerFactory.CreateLog().LogError("Invaild Fields : : " + fieldName);
                throw new MessageException(Errors.INVALID_PARAMETER, new string[] { fieldName });
            }
        }

        private bool ValidateMandatory(String value)
        {
            return !String.IsNullOrWhiteSpace(value);
        }

        private bool ValidateNumber(String value)
        {
            if (!String.IsNullOrEmpty(value))
            {
                var numberOnlyPattern = "^[0-9]*$";
                return System.Text.RegularExpressions.Regex.IsMatch(value.Trim(), numberOnlyPattern);
            }
            return true;
        }

        private bool ValidateDecimal(String value)
        {
            if (!String.IsNullOrEmpty(value))
            {
                Decimal dec;
                return Decimal.TryParse(value, out dec);
            }
            return true;
        }

        private bool ValidateDecimal(String value, Int32 scale, Int32 precision)
        {
            if (!String.IsNullOrEmpty(value) && scale > 0 && precision > scale)
            {
                var checklenDecimal = @"(^[0-9]{1," + (precision - scale).ToString() + @"})(\.[0-9]{1," + scale.ToString() + @"})?$";
                return System.Text.RegularExpressions.Regex.IsMatch(value, checklenDecimal);
            }
            return true;
        }

        private bool ValidateDate(String value)
        {
            if (!string.IsNullOrWhiteSpace(value))
            {
                DateTime tempDT;
                return DateTime.TryParseExact(value, ApplicationConstant.FORMAT_DATETIME_YYYYMMDD_DASH, ApplicationConstant.EN_DTFI, DateTimeStyles.None, out tempDT);
            }
            return true;
        }

        private bool ValidateTime(String value)
        {
            if (!string.IsNullOrWhiteSpace(value))
            {
                DateTime tempDT;
                return DateTime.TryParseExact(value, ApplicationConstant.FORMAT_DATETIME_HHMMSS_COLON, ApplicationConstant.EN_DTFI, DateTimeStyles.None, out tempDT);
            }
            return true;
        }

        private bool ValidateDateTime(String value)
        {
            if (!string.IsNullOrWhiteSpace(value))
            {
                DateTime tempDT;
                return DateTime.TryParseExact(value, ApplicationConstant.FORMAT_DATETIME_YYYYMMDDHHMMSS_DASH_COLON, ApplicationConstant.EN_DTFI, DateTimeStyles.None, out tempDT);
            }
            return true;
        }

        /// <summary>
        /// Validate length with Min/Max.
        /// </summary>
        /// <param name="value"></param>
        /// <param name="min">-1 = not applicable</param>
        /// <param name="max">>-1 = not applicable</param>
        /// <returns></returns>
        private int ValidateLength(String value, int min, int max)
        {
            if (!string.IsNullOrEmpty(value))
            {
                // Check minimum
                if (min != -1 && value.Trim().Length < min)
                {
                    return -1;
                }
                // Check maximum
                else if (max != -1 && value.Trim().Length > max)
                {
                    return 1;
                }
            }
            return 0;
        }

        /// <summary>
        /// Validate value match with one of possibleValues
        /// </summary>
        /// <param name="value"></param>
        /// <param name="possibleValues"></param>
        /// <returns></returns>
        private bool IsMatch(String value, String[] possibleValues)
        {
            bool result = true;
            if (possibleValues != null && possibleValues.Length > 0 && !String.IsNullOrEmpty(value))
            {
                result = false;
                foreach (String possibleValue in possibleValues)
                {
                    if (possibleValue.Equals(value))
                    {
                        result = true;
                        break;
                    }
                }
            }
            return result;
        }
        #endregion
        #region Constances
        public enum MessageType
        {
            STRING, NUMBER, DECIMAL, DATE, TIME, DATETIME
        }

        public enum Predicate
        {
            EQ, NE, LT, LE, GE, GT
        }
        #endregion Constances
    }
}